iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
自我挑戰組

Vscode 實作:用 HTML/CSS/JS 打造醫藥健康小工具箱系列 第 28 篇

Day 28:【系統整合】Tab 切換選單:用 JS 實作單頁應用(SPA)風格的 4 個工具頁面切換

  • 分享至 

  • xImage
  •  

本篇文章目錄

  1. 前言:從多頁面跳轉到流暢的 SPA 切換體驗
  2. 兩大核心機制拆解:狀態歸零與動態 DOM 綁定
  3. 實戰 JS 開發:Tab 切換邏輯與屬性容錯
  4. 程式碼重點與體驗細節解析
  5. 結語

一、 前言:從多頁面跳轉到流暢的 SPA 切換體驗

在過往的網頁開發模式中,如果一個網站有 4 個不同的功能工具,傳統的做法往往是建立 4 個獨立的 HTML 檔案(例如 tool1.html、tool2.html)。當使用者點擊選單時,瀏覽器就會觸發整頁重新載入,不僅畫面會出現短暫的白頁閃爍,連帶運算狀態與 UI 互動感也會被打斷。

隨著前端技術的演進,單頁應用(Single Page Application, SPA) 成為現代 Web App 的標準配置。

來到鐵人賽的 Day 28,我們前幾天陸續實作完成的 4 大醫療保健輔助工具(生理指標計算器、兒童用藥劑量換算器、每日用藥時間表生成器、開瓶倒數計時器)已經全數完工。今天我們要進行重要的「全站系統整合」——撰寫全站主控腳本 app.js,透過純原生 JavaScript 實作 Tab 切換選單,打造免刷新的 SPA 流暢切換體驗!

二、 兩大核心機制拆解:狀態歸零與動態 DOM 綁定

在撰寫 Tab 切換邏輯時,許多初學者常犯的錯誤是「只顧著把點到的頁面打開,卻忘了把其他的頁面關掉」,導致多個工具重疊顯示在畫面上。

為了確保切換邏輯精準無誤,我們將切換流程拆解為以下兩大核心步驟:

1. 全局狀態歸零(Reset Phase):

當使用者點擊任意一個 Tab 按鈕時,第一時間清除所有按鈕與工具卡片的激活狀態:

  • 移除所有 .tab-btn 的 .active Class,並將無障礙屬性 aria-selected 統一重置為 "false"。
  • 移除所有 .tool-card 的 .active Class,將所有工具頁面隱藏。

2. 精準目標啟用(Activation Phase):

歸零完成後,再針對「被點擊的按鈕」與「對應的目標工具卡片」進行狀態啟用:

  • 將被點擊的按鈕加上 .active,並將 aria-selected 設定為 "true"。
  • 讀取按鈕上的自訂 HTML 屬性(如 data-target 或 data-tab),取得目標卡片的 ID。
  • 透過 document.getElementById() 找到對應的 DOM 元素,加上 .activeClass 讓該工具頁面順暢顯現。

三、 實戰 JS 開發:Tab 切換邏輯與屬性容錯

我們在 /app.js 中監聽 DOMContentLoaded 事件,確保 DOM 節點載入完成後綁定事件監聽器:

/app.js

document.addEventListener("DOMContentLoaded", () => {
    // 1. 抓取畫面上的按鈕與卡片
    const tabBtns = document.querySelectorAll(".tab-btn");
    const toolCards = document.querySelectorAll(".tool-card");

    // 2. 監聽每個按鈕的點擊事件
    tabBtns.forEach(btn => {
      btn.addEventListener("click", () => {

        // 3. 歸零:移除所有按鈕與卡片的 active 樣式,並將 aria-selected 設為 "false"
        tabBtns.forEach(b => {
          b.classList.remove("active");
          b.setAttribute("aria-selected", "false");
        });

        toolCards.forEach(c => {
          c.classList.remove("active");
        });

        // 4. 啟用:將被點擊的按鈕與對應的卡片加上 active 樣式,並將 aria-selected 設為 "true"
        btn.classList.add("active");
        btn.setAttribute("aria-selected", "true");

        // 5. 讀取 HTML 屬性 (優先讀取 data-target,若無則讀取 data-tab)
        const targetTab = btn.getAttribute("data-target") || btn.getAttribute("data-tab");

        const targetCard = document.getElementById(targetTab);
        if (targetCard) {
          targetCard.classList.add("active");
        } else {
          console.error(`找不到 ID 為 "${targetTab}" 的工具卡片!請檢查 HTML 的 ID 命名。`);
        }

      });
    });
  });

四、 程式碼重點與體驗細節解析

1. DOMContentLoaded 確保 DOM 節點安全存取

將切換邏輯包裹在 document.addEventListener("DOMContentLoaded", ...) 中,能確保 JavaScript 執行時,HTML 檔案中的 .tab-btn 與 .tool-card 節點都已經完成解析與繪製,徹底杜絕因腳本過早執行而抓取到 null 的經典 Bugs。

2. 無障礙網頁設計(A11y)aria-selected 屬性動態同步

在進行頁面切換時,除了變更 CSS Class(.active)來呈現視覺效果外,我們更同步更新了 b.setAttribute("aria-selected", "false") 與 btn.setAttribute("aria-selected", "true")。

這個細節對於使用螢幕閱讀器(Screen Readers)的視障使用者極為重要,能讓無障礙輔助工具即時感知當前聚焦的是哪一個 Tab 頁面,大大提升全站的無障礙友善度。

3. 短路邏輯 || 實現 HTML 屬性命名向下相容(Fallback)

在大型專案或團隊協作中,跨頁面的 HTML 屬性命名可能偶有不一致(例如有人寫 data-target="tool1",有人寫 data-tab="tool1")。

透過 btn.getAttribute("data-target") || btn.getAttribute("data-tab") 的備援機制,能優先讀取 data-target,若不存在則自動降級讀取 data-tab,極大地增強了腳本的容錯率與彈性。

4. 開發者友善的除錯機制(Debug Log)

當 DOM 結構找不到對應 ID 的卡片時,程式並不會安靜地失敗或引發不可預期的錯誤,而是透過 console.error(...) 輸出明確且友善的錯誤訊息,精準提示開發者「找不到哪個 ID」,讓除錯(Debugging)過程變得更加直覺高效。

五、 結語

今天我們透過純原生 JavaScript 完成了全站的 Tab 切換選單,以極簡的程式碼量實現了單頁應用(SPA)級別的切換順暢度,順利將 4 大工具無縫整合於單一介面之下。


上一篇
Day 27:【全站記憶】 LocalStorage 應用(下):用 JSON.parse() 讀取歷史紀錄與一鍵清空 LocalStorage 資料庫
下一篇
Day 29:【部署上線】Git 基礎指令與 GitHub Pages 免費靜態網站託管實務
系列文
Vscode 實作:用 HTML/CSS/JS 打造醫藥健康小工具箱 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言